<?php

class ServiceWooly {
  
  // API
  const API_VERSION = '1.0';
  const API_METHOD = 'http';
  const API_DOMAIN = 'api.woo.ly';
  const API_BASEURL = '/url';
  
  // API Actions
  const ACTION_CREATE = 'create';
  const ACTION_GET = 'get';
  const ACTION_DELETE = 'delete';
  const ACTION_STATS = 'stats';
  const ACTION_DUMP = 'dump';
  const ACTION_USAGE = 'usage';
  const ACTION_HELP = 'help';
  
  // Expected API Mimetypes
  const MIMETYPE_XML = 'text/xml';
  const MIMETYPE_XML_ALT = 'application/xml';
  const MIMETYPE_JSON = 'application/json';
  const MIMETYPE_TEXT = 'text/plain';
  
  // API Formats
  const FORMAT_XML = 'xml';
  const FORMAT_JSON = 'json';
  const FORMAT_TEXT = 'txt';
  const FORMAT_BLANK = '';
  
  // Date range strings
  const RANGE_TODAY = 'today';
  const RANGE_YESTERDAY = 'yesterday';
  const RANGE_THIS_WEEK = 'thisweek';
  const RANGE_LAST_WEEK = 'lastweek';
  const RANGE_THIS_MONTH = 'thismonth';
  const RANGE_LAST_MONTH = 'lastmonth';
  const RANGE_LAST_YEAR = 'lastyear';
  const RANGE_THIS_YEAR = 'thisyear';
  const RANGE_ALL_TIME = 'alltime';
  
  // cURL Options
  const REQUEST_FOLLOWLOCATION = true;
  const REQUEST_MAXREDIRS = 5;
  const REQUEST_TIMEOUT = 10;
  const REQUEST_REFERER = 'php://ServiceWooly.php?version=1.0';
  const REQUEST_HEADER = true;
  const REQUEST_FAILONERROR = true;
  
  /**
   * @var array
   */
  protected $_validActions = array(
    self::ACTION_CREATE,
    self::ACTION_GET,
    self::ACTION_DELETE,
    self::ACTION_STATS ,
    self::ACTION_DUMP,
    self::ACTION_USAGE,
    self::ACTION_HELP
  );
  
  /**
   * @var array
   */
  protected $_validMimeTypes = array(
    self::MIMETYPE_XML,
    self::MIMETYPE_XML_ALT,
    self::MIMETYPE_JSON,
    self::MIMETYPE_TEXT
  );
  
  /**
   * @var array
   */
  protected $_validFormats = array(
    self::FORMAT_XML,
    self::FORMAT_JSON,
    self::FORMAT_TEXT,
    self::FORMAT_BLANK
  );
  
  /**
   * @var array
   */
  protected $_validDateRanges = array(
    self::DATE_TODAY,
    self::DATE_YESTERDAY,
    self::DATE_THIS_WEEK,
    self::DATE_LAST_WEEK,
    self::DATE_THIS_MONTH,
    self::DATE_LAST_MONTH,
    self::DATE_LAST_YEAR,
    self::DATE_THIS_YEAR,
    self::DATE_ALL_TIME
  );
  
  /**
   * @var array
   */
  protected $_cache = array();
  
  /**
   * @var array
   */
  protected $_mapMimetypeToFormat = array(
    self::MIMETYPE_XML => self::FORMAT_XML,
    self::MIMETYPE_XML_ALT => self::FORMAT_XML,
    self::MIMETYPE_JSON => self::FORMAT_JSON,
    self::MIMETYPE_TEXT => self::FORMAT_TEXT
  );
  
  /**
   * @var array
   */
  protected $_curlOptions = array(
    CURLOPT_FOLLOWLOCATION => self::REQUEST_FOLLOWLOCATION,
    CURLOPT_MAXREDIRECTS => self::REQUEST_MAXREDIRS,
    CURLOPT_TIMEOUT => self::REQUEST_TIMEOUT,
    CURLOPT_REFERER => self::REQUEST_REFERER,
    CURLOPT_HEADER => self::REQUEST_HEADER,
    CURLOPT_FAILONERROR => self::REQUEST_FAILONERROR
  );
  
  /**
   * @var array
   */
  protected $_lastRequest = array();
  
  /**
   * @var array
   */
  protected $_lastResponse = array();
  
  /**
   * @var array
   */
  protected $_defaultParams = array();
  
  /**
   * @var string
   */
  protected $_apikey;
  
  /**
   * @return null
   */
  public function __construct($apikey=null, $format=null) {
    $this->setApikey($apikey)
      ->setFormat($format);
  }
  
  /**
   * Allow access to all WoolyService::_valid* variables
   *
   * @return array 
   */
  public function __call($method, $arguments) {
    if (isset($this->_cache[$method])) {
      return $this->_cache[$method];
    }
    if (strstr($method, 'getValid') === 0) {
      $property = '_valid'.substr($method, 9);
      if (property_exists(__CLASS__, $this->{$property})) {
        $this->_cache[$method] = $this->{$property};
        return $this->{$property};
      }
    }
    trigger_error();
  }
  
  /**
   * @return ServiceWooly
   */
  public function setApikey($apikey=null) {
    $this->_apikey = $apikey;
    $this->_updateDefaultParams();
    return $this;
  }
  
  /**
   * @return ServiceWooly
   */
  public function setFormat($format=null) {
    if (null === $format || !in_array($format, $this->_validFormats)) {
      $this->_format = self::FORMAT_JSON;
    } else {
      $this->_format = $format;
    }
    return $this;
  }
  
  /**
   * Create a shortened URL for longURL
   */
  public function create($longUrl, $custom=null, $public=null, $useDomain=null) {
    $params = array('longurl' => $longUrl);
    if (null !== $custom) {
      $params['custom'] = $custom;
    }
    if (null !== $public) {
      $params['public'] = !!$public ? 1 : 0;
    }
    if (null !== $useDomain) {
      $params['usedomain'] = !!$useDomain ? 1 : 0;
    }
    return $this->sendRequest('create', $params);
  }
  
  /**
   * Get the longUrl for given id
   */
  public function get($id) {
    $id = (null === $id) ? '' : '/'.$id;
    return $this->sendRequest('get'.$id);
  }
  
  /**
   * Delete the longURL to shortened URL relationship for given id
   */
  public function delete($id) {
    $id = (null === $id) ? '' : '/'.$id;
    return $this->sendRequest('delete'.$id);
  }
  
  /**
   * Get the site stats
   */
  public function stats($id) {
    $id = (null === $id) ? '' : '/'.$id;
    return $this->sendRequest('stats'.$id);
  }
  
  /**
   * Get the stats over a period of time for your account?
   */
  public function dump() {
    $num = func_num_args();
    $arg = func_get_args();
    $params = array();
    switch($num) {
      case 2:
        $startDate = is_int($arg[0]) ? $arg[0] : strtotime($arg[0]);
        $startDate = (false === $startDate) ? 0 : $startDate;
        $endDate = is_int($arg[1]) ? $arg[1] : strtotime($arg[1]);
        $endDate = (false === $endDate) ? time() : $endDate;
        $params = array(
          'start' => $startDate, 'end' => $endDate
        );
        break;
      case 1:
        if (in_array($arg[0], $this->_validDateRanges)) {
          $dateRange = $arg[0];
        } else {
          $dateRange = self::DATE_ALL_TIME;
        }
        $params = array(
          'range' => $dateRange
        );
        break;
      default:
        $params = array(
          'range' => self::DATE_ALL_TIME
        );
        break;
    }
    return $this->sendRequest('dump', $params);
  }
  
  /**
   * Get your usage statistics
   */
  public function usage($apikey=null) {
    $params = array();
    if (null !== $apikey) {
      $params['apikey'] = $apikey;
    }
    return $this->sendRequest('usage', $params);
  }
  
  /**
   * Get instructions for given command
   */
  public function help($command=null) {
    $params = array();
    if (null !== $command) {
      $params['command'] = $command;
    }
    return $this->sendRequest('help', $params);
  }
  
  /**
   * Generate URL for request, send
   */
  public function sendRequest($action, $params=array(), $format=null, $baseUrl=null) 
  {
    $curRequest = array();
    
    // Override baseurl
    if (null === $baseUrl) {
      $baseUrl = self::API_BASEURL;
    }
    
    // Override default format
    if (null === $format || !in_array($format, $this->_validFormats)) {
      $format = $this->_format;
    }
    
    // Record request method, domain, baseurl, action, format
    $curRequest['method'] = self::API_METHOD;
    $curRequest['domain'] = self::API_DOMAIN;
    $curRequest['baseurl'] = $baseUrl;
    $curRequest['action'] = $action;
    $curRequest['format'] = $format;
    
    // Build query
    if (null === $params || !is_array($params)) {
      $params = array();
    }
    $curRequest['params'] = $this->_defaultParams + $params;
    $urlQuery = implode('&', array_map(
      create_function('$a,$b', 'return $b."=".urlencode($a);'), 
      $curRequest['params'], 
      array_keys($curRequest['params'])
    ));
    $curRequest['query'] = empty($urlQuery) ? '' : ('?'.$urlQuery);
    
    // Build URL
    $requestUrl = '%method%://%domain%%baseurl%/%action%%format%%query%';
    foreach($curRequest as $field => $value) {
      if ($field === 'format' && $value != self::FORMAT_BLANK) {
        $value = '.'.$value;
      }
      $requestUrl = str_replace('%'.$field.'%', $value, $requestUrl);
    }
    $curRequest['url'] = $requestUrl;
    
    // Record request
    $this->_lastRequest = $curRequest;
    
    // Build request
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $curRequest['url']);
    foreach($this->_curlOptions as $curlopt => $value) {
      curl_setopt($curl, $curlopt, $value);
    }
    
    return $curRequest['url'];
    
    $response = curl_exec($curl);
    
    // Success
    if ($response !== false) {
      $pos = strstr($response, "\n\n");
      if ($pos !== false) {
        $header = substr($response, 0, $pos);
        $headers = array();
        foreach(implode("\n", $header) as $line) {
          if (!empty($line) && preg_match('/^([^:]+):(.*)$/', $line, $matches)) {
            $headers[trim($matches[1])] = trim($matches[2]);
          }
        }
        $body = trim(substr($response, $pos, strlen($response) - $pos));
      
      // TODO Unable to find HTTP header
      } else {
        
      }
    }
  }
  
  protected function _updateDefaultParams()
  {
    if (null === $this->apikey || !is_string($this->_apikey)) {
      unset($this->_defaultParams['apikey']);
    } else {
      $this->_defaultParams['apikey'] = $this->_apikey;
    }
    return $this;
  }
}

class ServiceWoolyException extends Exception {}